package cn.shadow.module.scene;


import cn.shadow.utils.Utils;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.InsufficientCapacityException;

import java.util.Collection;

import java.util.Map;
import java.util.concurrent.*;

public class ViewModule implements IViewModule , RingEventHandler<ViewModule.RemoveFightModuleEvent>{

    /**
     * 所有的场景对象
     */
    private Map<Long, SceneObj> allSceneObjs = new ConcurrentHashMap<>();


    /**
     * 用于处理当玩家或者怪物离开场景,删除其他可以看到他的玩家的信息
     */
    private final RingBufferWrapper<RemoveFightModuleEvent> toRemoveBuffer;



    @Override
    public void handle(RemoveFightModuleEvent event) {
        doAfterRemoved(event.removedViewObj, event.needSendMsgWhenRemove, true);
    }

    static class RemoveFightModuleEvent implements RingBufferEvent {
        static final EventFactory<RemoveFightModuleEvent> FACTORY = RemoveFightModuleEvent::new;

        ViewObj removedViewObj;
        boolean needSendMsgWhenRemove;

        @Override
        public void handle() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void cleanUp() {
            // 清空掉
            this.removedViewObj = null;
        }
    }

    private final int viewId = 1;

    private ViewGrids viewGrids;

    public ViewModule(int w, int h) {
        this.viewGrids = new ViewGrids(w, h, false);
        this.toRemoveBuffer = new RingBufferWrapper<>(RemoveFightModuleEvent.FACTORY, 4096, false);
        ringBufferFullToRemoveObjs = new LinkedTransferQueue<>();
    }

    public static void main(String[] args) {
        ViewModule viewModule = new ViewModule(3600, 3600);
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i < 50; i++) {
            viewModule.add(new HeroFightModule());
        }

        service.scheduleAtFixedRate(viewModule::rebuildInViewList, 0, 5000, TimeUnit.MILLISECONDS);

        try {
            Thread.sleep(5000);
            viewModule.remove(viewModule.allSceneObjs.values().stream().findFirst().get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void move() {
        for (SceneObj value : allSceneObjs.values()) {
            int x = (int) (Math.random() * ((viewGrids.maxXIdx+1) * viewGrids.viewGridLength));
            int y = (int) (Math.random() *((viewGrids.maxYIdx+1) * viewGrids.viewGridLength));
            value.setX(x);
            value.setY(y);
        }
    }

    @Override
    public boolean add(SceneObj sceneObj) {
        SceneObj obj = allSceneObjs.putIfAbsent(sceneObj.getId(), sceneObj);
        if (obj != null) {
            return false;
        }
        //执行添加到视野后的逻辑
        sceneObj.afterAddToScene();
        return true;
    }

    @Override
    public boolean remove(SceneObj sceneObj) {
        SceneObj obj = allSceneObjs.remove(sceneObj.getId());
        if (obj == null) return false;
        doAfterRemoved(sceneObj.getViewObj(), true, false);
        return true;
    }

    /**
     * @param viewObj         视野对象
     * @param needMsg         移除是否需要发送消息
     * @param inReFreshThread 在刷新线程中处理
     */
    private void doAfterRemoved(ViewObj viewObj, boolean needMsg, boolean inReFreshThread) {
        ViewGrid viewGrid = viewObj.getViewGrid();
        if (inReFreshThread) {
            if (viewGrid == null) { //没有刷新过视野,直接返回
                return;
            }
            viewGrid.removeObj(viewObj.getId()); //当前的视野格子移除当前场景对象
            if (viewObj instanceof HeroViewObj) {
                removeHeroInterestGridsWhenLeaveScene((HeroViewObj) viewObj, viewGrid);
            }
            viewObj.setViewGrid(null);
            return;
        }
        // 如果此刻玩家再次进入了该场景,而且先刷新过了视野里面可以见到的人了

        if (needMsg && viewGrid != null) {
            for (HeroViewObj heroViewObj : viewGrid.getInterestHero().values()) {
                if (heroViewObj != null && heroViewObj != viewObj &&
                        viewObj.isHeroCanSeeMe(heroViewObj)) {
                    // 对方能够看到我,那么给对方发送我离开的消息
                    viewObj.doRemoveFromHero(heroViewObj);
                }
            }
        }
        if (viewObj instanceof HeroViewObj) {
            this.allSceneObjs.remove(viewObj.getId());
        }

        try {
            long sequence = this.toRemoveBuffer.tryNext();
            try {
                RemoveFightModuleEvent event = this.toRemoveBuffer.get(sequence);
                event.removedViewObj = viewObj;
                event.needSendMsgWhenRemove = needMsg;
            } finally {
                this.toRemoveBuffer.publish(sequence);
            }
        } catch (InsufficientCapacityException ex) {
            this.ringBufferFullToRemoveObjs.add(viewObj); // 满了就用其他方式处理
        }

    }
    private  LinkedTransferQueue<ViewObj> ringBufferFullToRemoveObjs;
    /**
     * 如果是玩家,移除出去时需要通知对这个格子感兴趣的所有玩家
     *
     * @param viewObj
     * @param viewGrid
     */
    private void removeHeroInterestGridsWhenLeaveScene(HeroViewObj viewObj, ViewGrid viewGrid) {
        int viewRange = viewObj.getViewRange();
        int minXIdx = Math.max(0, viewGrid.xIdx - viewRange);
        int minYIdx = Math.max(0, viewGrid.yIdx - viewRange);

        int maxXIdx = Math.min(viewGrids.maxXIdx, viewGrid.xIdx + viewRange);
        int maxYIdx = Math.min(viewGrids.maxYIdx, viewGrid.yIdx + viewRange);

        for (int i = minXIdx; i <= maxXIdx; i++) {
            for (int j = minYIdx; j <= maxYIdx; j++) {
                ViewGrid curViewGrid = this.viewGrids.getViewGridByIdx(i, j);
                if (curViewGrid == null) continue;
                viewGrid.unInterest(viewObj);
            }
        }
    }


    @Override
    public SceneObj getObject(long id) {
        return allSceneObjs.get(id);
    }

    /**
     * 刷新所有对象的视野
     */
    @Override
    public void rebuildInViewList() {
        processRemoveEvent();
        move();
        for (SceneObj sceneObj : allSceneObjs.values()) {
            updateSceneObjView(0L, sceneObj);

        }
    }

    /**
     * 刷新场景视野
     */
    private void updateSceneObjView(long ctime, SceneObj sceneObj) {
        ViewObj viewObj = sceneObj.getViewObj();

        int viewId = viewObj.getViewId();
        if (viewId != this.viewId) { //不在当前场景内
            return;
        }
        if (!viewObj.isViewChanged(sceneObj.getX(), sceneObj.getY())) {
            // 没有变化
            return;
        }

        ViewGrid newGrid = this.viewGrids.getViewGrid(viewObj.getX(), viewObj.getY());
        if (newGrid == null) {
            return; //越界了，没取到视野格子
        }

        ViewGrid oldViewGrid = viewObj.getViewGrid();
        if (oldViewGrid == null) {
            //第一次进入场景
            updateCanSeeMeObjs(ctime, sceneObj, viewObj, null, newGrid);

            if (viewObj instanceof HeroViewObj) {
                // 计算出所有感兴趣的点
                // 把那些所有我感兴趣的东西都发给我
                HeroViewObj heroViewObj = (HeroViewObj) viewObj;
                int newViewRange = heroViewObj.syncViewRange();
                updateHeroInterestGridsWhenEnterScene(heroViewObj, newGrid, newViewRange, ctime);
            }
            return;
        }

        boolean isGridChanged = oldViewGrid != newGrid; //是否已经换了格子

        if (isGridChanged) {
            updateCanSeeMeObjs(ctime, sceneObj, viewObj, oldViewGrid, newGrid);

        }
        if (!(viewObj instanceof HeroViewObj)) { //角色可能调了视野范围
            return;
        }
        // 英雄可能更新了感兴趣的区域
        HeroViewObj heroViewObj = (HeroViewObj) viewObj;

        int oldViewRange = heroViewObj.getViewRange();
        int newViewRange = heroViewObj.syncViewRange();

        if (!isGridChanged && oldViewRange == newViewRange) {
            // 视野大小以及所在格子都没变,不管
            return;
        }

        // 计算出所有感兴趣的点
        // 把那些所有我感兴趣的东西都发给我
        updateHeroInterestGridsWhenChangeGrid(heroViewObj, oldViewGrid, oldViewRange, newGrid,
                newViewRange, ctime);
        System.out.println(String.format("玩家%s" +
                        "当前坐标(x=%s,y=%s),之前所在格子【%s,%s】目前所在格子【gx=%s,gy=%s】,当前格子的兴趣列表为：%s",
                sceneObj.getId(), sceneObj.getX(), sceneObj.getY(),oldViewGrid.xIdx,oldViewGrid.yIdx, newGrid.xIdx, newGrid.yIdx, newGrid.getInterestHero().size()));
    }

    /**
     * @param heroViewObj  玩家自己
     * @param oldViewGrid  老视野格子
     * @param oldViewRange 老视野范围
     * @param newGrid      新视野格子
     * @param newViewRange 新视野范围
     * @param ctime        这里有一个问题要注意，玩家视野范围变化有两种结果：
     *                     小--->大:需要将大小交集之外的区域感兴趣
     *                     大--->小：需要将大小交集之外的区域不感兴趣
     */
    private void updateHeroInterestGridsWhenChangeGrid(HeroViewObj heroViewObj, ViewGrid oldViewGrid, int oldViewRange, ViewGrid newGrid, int newViewRange, long ctime) {

        int oldMinXIdx = Math.max(0, oldViewGrid.xIdx - oldViewRange);
        int oldMinYIdx = Math.max(0, oldViewGrid.yIdx - oldViewRange);

        int oldMaxXIdx = Math.min(viewGrids.maxXIdx, oldViewGrid.xIdx + oldViewRange);
        int oldMaxYIdx = Math.min(viewGrids.maxYIdx, oldViewGrid.yIdx + oldViewRange);

        int newMinXIdx = Math.max(0, newGrid.xIdx - newViewRange);
        int newMinYIdx = Math.max(0, newGrid.yIdx - newViewRange);

        int newMaxXIdx = Math.min(viewGrids.maxXIdx, newGrid.xIdx + newViewRange);
        int newMaxYIdx = Math.min(viewGrids.maxYIdx, newGrid.yIdx + newViewRange);

        // 先感兴趣，再不感兴趣，不然部分数据玩家收不到
        for (int xIdx = newMinXIdx; xIdx <= newMaxXIdx; xIdx++) {
            for (int yIdx = newMinYIdx; yIdx <= newMaxYIdx; yIdx++) {
                if (Utils.isInRectangle(oldMinXIdx, oldMinYIdx, oldMaxXIdx, oldMaxYIdx, xIdx, yIdx)) {
                    continue;
                }
                ViewGrid viewGrid = this.viewGrids.getViewGridByIdx(xIdx, yIdx);
                viewGrid.onInterest(heroViewObj, ctime);
            }
        }

        for (int xIdx = oldMinXIdx; xIdx <= oldMaxXIdx; xIdx++) {
            for (int yIdx = oldMinYIdx; yIdx <= oldMaxYIdx; yIdx++) {
                if (Utils.isInRectangle(newMinXIdx, newMinYIdx, newMaxXIdx, newMaxYIdx, xIdx, yIdx)) {
                    continue;
                }
                ViewGrid viewGrid = this.viewGrids.getViewGridByIdx(xIdx, yIdx);
                if (viewGrid == null) {
                    continue;
                }
                viewGrid.unInterest(heroViewObj);
            }
        }
    }

    /**
     * 当玩家第一次进入场景的时候,刷新玩家感兴趣的格子
     *
     * @param heroViewObj
     * @param newGrid
     * @param newViewRange
     * @param ctime
     */
    private void updateHeroInterestGridsWhenEnterScene(HeroViewObj heroViewObj, ViewGrid newGrid, int newViewRange, long ctime) {
        int minXIdx = Math.max(0, newGrid.xIdx - newViewRange);
        int minYIdx = Math.max(0, newGrid.yIdx - newViewRange);

        int maxXIdx = Math.min(viewGrids.maxXIdx, newGrid.xIdx + newViewRange);
        int maxYIdx = Math.min(viewGrids.maxYIdx, newGrid.yIdx + newViewRange);

        for (int i = minXIdx; i <= maxXIdx; i++) {
            for (int j = minYIdx; j <= maxYIdx; j++) {
                ViewGrid viewGrid = this.viewGrids.getViewGridByIdx(i, j);
                if (viewGrid == null) continue;
                viewGrid.onInterest(heroViewObj, ctime);
            }
        }
    }

    /**
     * 处理玩家进入场景时，更新其他对象视野
     * 将我的消息发送给其他玩家
     *
     * @param ctime       时间
     * @param sceneObj    场景对象
     * @param viewObj     视野对象
     * @param oldViewGrid 之前的格子
     * @param newGrid     现在所处的格子
     */
    private void updateCanSeeMeObjs(long ctime, SceneObj sceneObj,
                                    ViewObj viewObj,
                                    ViewGrid oldViewGrid,
                                    ViewGrid newGrid) {

        if (oldViewGrid == null) {
            //第一次进入场景
            System.out.println(String.format("玩家%s" +
                            ",坐标(x=%s,y=%s),第一次进入格子【%s,%s】,当前格子的兴趣列表为：%s",
                    sceneObj.getId(), sceneObj.getX(), sceneObj.getY(),
                    newGrid.xIdx, newGrid.yIdx, newGrid.getInterestHero().size()));
            Collection<HeroViewObj> interestHero = newGrid.getInterestHero().values();
            for (HeroViewObj heroViewObj : interestHero) {
                if (viewObj != heroViewObj && viewObj.isHeroCanSeeMe(heroViewObj)) {
                    sceneObj.doSendMeToHero(heroViewObj, ctime, false);
                }
            }
        } else {
            Collection<HeroViewObj> interestHero = oldViewGrid.getInterestHero().values();
            for (HeroViewObj heroViewObj : interestHero) { //之前的格子里面视野对象没在新的格子里面
                if (viewObj != heroViewObj &&
                        viewObj.isHeroCanSeeMe(heroViewObj)
                        && !newGrid.isInInterest(heroViewObj.getId())) {
                    sceneObj.getViewObj().doRemoveFromHero(heroViewObj); //告诉对之前格子感兴趣的角色自己离开了
                }
            }
            oldViewGrid.removeObj(viewObj.getId()); //将场景对象移除这个格子

            //告诉新格子的兴趣列表，自己加进来了
            for (HeroViewObj heroViewObj : interestHero) {
                if (!oldViewGrid.isInInterest(heroViewObj.getId()) && viewObj != heroViewObj && viewObj.isHeroCanSeeMe(heroViewObj)) {
                    sceneObj.doSendMeToHero(heroViewObj, ctime, false);
                }
            }
        }
        newGrid.addObj(sceneObj);
        viewObj.setViewGrid(newGrid);

    }

    /**
     * 执行场景对象移除事件
     */
    private void processRemoveEvent() {
        this.toRemoveBuffer.handleEvents(this);
        for (ViewObj removedViewObj = this.ringBufferFullToRemoveObjs.poll();
             removedViewObj != null; removedViewObj = this.ringBufferFullToRemoveObjs.poll()) {
            try {
                doAfterRemoved(removedViewObj, true, true);
            } catch (Throwable e) {
                System.out.println(("ViewPositionModule.processRemoveExents() 执行出错: {}"+ e));
            }
        }
    }

}
